#include <windows.h>
#include <BaseTsd.h>

#include <algorithm>
#include <set>
#include <sys/stat.h>

#include "log.h"
#include "internal/FakeRoot.h"

#define FAKE_DIR_MODE (_S_IFDIR | 0755)
#define FAKE_DIR_SIZE 4096

bool FakeRoot::TranslatePath(const PathMapper& lockedPathMapper, const char* hostPath,
                             std::string& shareName, std::string& fullPath, bool& root, bool& fake)
{
    std::string Path(&hostPath[1]);
    std::string::size_type end;
    root = false;
    fake = false;

    if (hostPath[0] != '/') {
        return false;
    }

    shareName = "";
    fullPath = "";
    end = Path.find_first_of("/");
    if (end != std::string::npos) {
        shareName.append(Path.begin(), Path.begin() + end);
    } else if (hostPath[1] != '\0') {
        shareName.append(Path);
        auto pathMapIter = lockedPathMapper.find(shareName);
        if (pathMapIter != lockedPathMapper.end()) {
            if (!pathMapIter->second.fake) {
                fullPath.append(pathMapIter->second.fileMap.begin()->second);
            } else {
                fake = true;
            }
            return true;
        } else {
            return false;
        }
    } else {
        root = true;
        fake = true;
        return true;
    }

    auto pathMapIter = lockedPathMapper.find(shareName);
    if (pathMapIter == lockedPathMapper.end()) {
        return false;
    }
    const auto& shareContent = pathMapIter->second;
    if (shareContent.fake) {
        std::string fakeName;
        std::string::size_type fakeNameStart = end + 1;
        std::string::size_type fakeNameEnd = Path.find_first_of("/", fakeNameStart);
        if (fakeNameEnd != std::string::npos) {
            fakeName.append(std::begin(Path) + fakeNameStart, std::begin(Path) + fakeNameEnd);
        } else {
            fakeName.append(std::begin(Path) + fakeNameStart, std::end(Path));
        }

        auto fileMapIter = shareContent.fileMap.find(fakeName);
        if (fileMapIter != shareContent.fileMap.end()) {
            fullPath.append(fileMapIter->second);
            if (fakeNameEnd != std::string::npos) {
                fullPath.append(std::begin(Path) + fakeNameEnd, std::end(Path));
            }
        } else {
            LOGE("Fake name not found.");
            return false;
        }
    } else {
        fullPath.append(shareContent.fileMap.begin()->second);
        fullPath.append(std::begin(Path) + end, std::end(Path));
    }
    std::replace(fullPath.begin(), fullPath.end(), '/', '\\');
    return true;
}

int FakeRoot::Open(GuestFuseSrvRPL* rpl)
{
    rpl->hdr.len = 0;
    return RETURN_SUCCESS;
}

int FakeRoot::GetAttr(GuestFuseSrvRPL* rpl)
{
    GuestFuseSrvFileMetaData* md = &rpl->file[0].md;

    rpl->hdr.len = sizeof(GuestFuseSrvFileMetaData);

    md->mode = (uint64_t)FAKE_DIR_MODE;
    md->size = (uint64_t)FAKE_DIR_SIZE;
    md->pathLen = 0;
    md->padLen = 0;

    return RETURN_SUCCESS;
}

int FakeRoot::ReadDir(
    const std::vector<std::pair<const std::string&, bool>>& contentList,
    GuestFuseSrvRPL* rpl, int rplBuffSz)
{
    int ret;
    uint8_t* rplStart = (uint8_t*)rpl;
    uint8_t* rplData = (uint8_t*)&rpl->file[0];

    for (const auto& [path, hidden] : contentList) {
        if (hidden)
            continue;

        if ((rplData - rplStart + sizeof(GuestFuseSrvFileMetaData)) >= rplBuffSz) {
            LOGE("Fake ReadDir reply buffer too small: {}", rplBuffSz);
            rpl->hdr.len = 0;
            return RETURN_ERROR;
        }

        const char* shareName = path.c_str();
        GuestFuseSrvFile* guestFuseSrvFile = (GuestFuseSrvFile*)rplData;
        ZeroMemory(&guestFuseSrvFile->md, sizeof(guestFuseSrvFile->md));

        guestFuseSrvFile->md.pathLen = (uint32_t)strlen(shareName) + 1;
        guestFuseSrvFile->md.padLen =
            RPL_PATH_ALIGN - (guestFuseSrvFile->md.pathLen & (RPL_PATH_ALIGN - 1));
        uint32_t entryLen =
            (uint32_t)sizeof(guestFuseSrvFile->md) + guestFuseSrvFile->md.pathLen + guestFuseSrvFile->md.padLen;

        if ((rplData - rplStart + entryLen) > rplBuffSz) {
            LOGE("Fake ReadDir reply buffer too small: {}", rplBuffSz);
            rpl->hdr.len = 0;
            return RETURN_ERROR;
        }

        ret = memcpy_s(guestFuseSrvFile->path, PATH_MAX, shareName, guestFuseSrvFile->md.pathLen);
        if (ret != 0) {
            LOGE("Execution of memcpy_s fails with error: {}", ret);
            break;
        }

        rpl->hdr.len += entryLen;
        rplData += entryLen;
    }

    return RETURN_SUCCESS;
}
